/*
 * DirectoryReaderLinked.cpp
 *
 *  Created on: Mar 26, 2010
 *      Author: Lukasz Spintzyk
 */

#include "DirectoryReaderLinked.h"
#include "DirEntry.h"
#include "Inode.h"
#include "InodeReader.h"
#include "BlockReader.h"
#include "SuperBlock.h"
#include "FSException.h"

#include "fs_config.h"
#include<boost/tokenizer.hpp>


#include "ph.h"



/**!
 * Class parses blocks of memory with directory data.
 */

DirectoryReaderLinked::DirectoryReaderLinked(BlockManager& b, InodeManager& i)
	:DirectoryReader(b,i)
{
	d_buffer = new byte[8*1024*10];
}

/*****************************************************************************/
/*****************************************************************************/

DirectoryReaderLinked::~DirectoryReaderLinked()
{
	delete[] d_buffer;
}

/*****************************************************************************/
/*****************************************************************************/

void DirectoryReaderLinked::setUpRootDirectory(uint16_t uid, uint16_t gid)
{
	InodePtr root = Inode::newInstance();
	root->d_mode = S_IFDIR | 0755;
	root->d_uid = uid;
	root->d_size = 0;
	root->d_atime = time(NULL);
	root->d_ctime = time(NULL);
	root->d_mtime = time(NULL);
	root->d_dtime = time(NULL);
	root->d_gid = gid;
	root->d_linksCount = 1;
	root->d_blocks = 0;
	root->d_flags = 0;
	root->d_osd1 = EXT2_OS_LINUX;
	root->d_num = EXT2_ROOT_INO;

	d_inodeMgr.setInode(EXT2_ROOT_INO,root);

	d_inodeMgr.reserveBlocksForInode(root, ROOT_RESERVED_BLOCKS_NUM);

	setUpDirectory(root, EXT2_ROOT_INO);

	root->d_size = d_sb.d_blockSize;

	d_inodeMgr.setInode(EXT2_ROOT_INO,root);
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t DirectoryReaderLinked::findLastEntry(InodePtr inode, DirEntry& last)
{
	uint32_t block_size = d_sb.d_blockSize;
	assert(inode->d_block!= 0);

	uint32_t last_block = d_inodeMgr.getLastUsedBlockNumber(inode);
	if ((last_block == 0))
	{
		last_block = inode->d_block[0];
	}

	d_blockMgr.getBlock(last_block, d_buffer);

	bool last_entry  = false;
	uint32_t pos = 0;
	while (!last_entry &&
			pos < block_size)
	{
		last.read(d_buffer+pos);
		if (last.d_recordLen + pos >= block_size)
		{
			break;
		}
		pos+=last.d_recordLen;
	}

	return pos;
}

/*****************************************************************************/
/*****************************************************************************/

void DirectoryReaderLinked::addDirEntry(DirEntry& e, InodePtr i)
{
	// TODO test function
	assert(i->isDir());
	assert(i->d_blocks > 0);
	// TODO need reserve blocks ?
	// TODO make sure that not empty entries are good handled.
	assert(i->d_size);

	DirEntry last_entry;
	uint32_t last_pos = findLastEntry(i, last_entry);
	uint32_t block_size = d_sb.d_blockSize;

	uint32_t last_block = d_inodeMgr.getLastUsedBlockNumber(i);

	int32_t diff = last_entry.d_recordLen - last_entry.size() - e.size();
	if (diff >=0 )
	{
		//there is place after last_entry in current block

		last_entry.d_recordLen = last_entry.size();
		last_entry.write(d_buffer + last_pos);
		assert(block_size >= last_pos + last_entry.size());
		e.d_recordLen = block_size  - last_pos - last_entry.size();
		e.write(d_buffer + last_pos + last_entry.size());
		d_blockMgr.setBlock(last_block, d_buffer);
	}
	else
	{
		//get Next block
		uint32_t cur = i->d_size / block_size;
		while (cur >= i->d_blocks)
		{
			d_inodeMgr.reserveBlocksForInode(i, 1);
			cur = i->d_size / block_size;
		}
		i->d_size += block_size;
		e.d_recordLen = block_size;
		cur = d_inodeMgr.blkIndex2BlkNum(i, cur);
		e.write(d_buffer);
		d_blockMgr.setBlock(cur, d_buffer);
	}
}

/*****************************************************************************/
/*****************************************************************************/

void DirectoryReaderLinked::setUpDirectory(InodePtr inode, uint32_t parent_inode_num)
{
	uint32_t block_size = d_sb.d_blockSize;
	DirEntry thisDir;
	thisDir.d_name = ".";
	thisDir.d_inodeNum = inode->d_num;
	thisDir.d_fileType = EXT2_FT_DIR;
	thisDir.d_recordLen = thisDir.size();

	DirEntry parentDir;
	parentDir.d_name = "..";
	parentDir.d_inodeNum = parent_inode_num;
	parentDir.d_fileType = EXT2_FT_DIR;
	parentDir.d_recordLen = block_size - thisDir.size();

	memset(d_buffer, 0, block_size);

	thisDir.write(d_buffer);
	parentDir.write(d_buffer + thisDir.size());
	d_blockMgr.setBlock(inode->d_block[0], d_buffer);
	inode->d_size = d_sb.d_blockSize;
}

/*****************************************************************************/
/*****************************************************************************/

InodePtr DirectoryReaderLinked::path2Inode(const std::string& path)
{
	return relPath2Inode(EXT2_ROOT_INO, path);
}

/*****************************************************************************/
/*****************************************************************************/

InodePtr DirectoryReaderLinked::entryNameInParentDir2Inode(InodePtr parentDir, const std::string& entry_name)
{
	boost::shared_ptr<DirectoryIterator> it = begin(parentDir);
	while (!it->end())
	{
		const DirEntry& e = **it;
		if (e.d_name == entry_name){
			return d_inodeMgr.getInode(e.d_inodeNum );
		}
		++(*it);
	}

	throw FSException(ENOENT, "No such file or directory" + entry_name);
}

/*****************************************************************************/
/*****************************************************************************/

InodePtr DirectoryReaderLinked::relPath2Inode(const uint32_t parent_dir_inode_num, const std::string& relative_path)
{
	// TODO test function

	boost::escaped_list_separator< char > separator('\\', '/', '\"'); // TODO is " necessary ?
	boost::tokenizer<boost::escaped_list_separator< char > > tok(relative_path, separator);

	boost::tokenizer<boost::escaped_list_separator<char> >::iterator beg = tok.begin();

	InodePtr parentDirInode = d_inodeMgr.getInode(parent_dir_inode_num);
	assert(parentDirInode->d_num = parent_dir_inode_num);

	if ( relative_path ==  "/" )
	{
		return parentDirInode;
	}

	for (boost::tokenizer<boost::escaped_list_separator<char> >::iterator
			entryName = tok.begin(); entryName!= tok.end(); ++entryName)
	{
		if ((*entryName).empty())
		{
			continue;
		}
		parentDirInode = entryNameInParentDir2Inode(parentDirInode, *entryName);
	}

	return parentDirInode;
}

/*****************************************************************************/
/*****************************************************************************/

boost::shared_ptr<DirectoryIterator> DirectoryReaderLinked::begin(InodePtr inode)
{
	return boost::shared_ptr<DirectoryIterator>(
			new DirectoryIterator(inode, this));
}
/*****************************************************************************/
/*****************************************************************************/

DirectoryIterator::DirectoryIterator(InodePtr i, DirectoryReader* dirReader)
	:d_inode(i)
	,d_pos(0)
	,d_prevPos(0)
	,d_block(0)
	,d_dirReader(dirReader)
	,d_buffer(new byte[d_dirReader->d_sb.d_blockSize])
	,d_nodeNum(0)
{
	uint32_t block = d_dirReader->d_inodeMgr.blkIndex2BlkNum(d_inode, 0);
	d_dirReader->d_blockMgr.getBlock(block, d_buffer);
	d_entry.read(d_buffer);
}

/*****************************************************************************/
/*****************************************************************************/

void DirectoryIterator::operator++()
{
	d_prevPos = d_pos;
	d_pos+=d_entry.d_recordLen;
	if (d_pos >= d_dirReader->d_sb.d_blockSize)
	{
		d_prevPos = d_pos = 0;
		d_block++;
		uint32_t new_block = d_dirReader->d_inodeMgr.blkIndex2BlkNum(d_inode, d_block);
		d_dirReader->d_blockMgr.getBlock(new_block, d_buffer);
	}
}

/*****************************************************************************/
/*****************************************************************************/

DirEntry DirectoryIterator::operator*()
{
	d_entry.read(d_buffer + d_pos);
	return d_entry;
}

/*****************************************************************************/
/*****************************************************************************/

bool DirectoryIterator::removeEntry()
{

	if (d_pos == 0)
	{
		//write
		d_entry.d_name = "";
		d_entry.write(d_buffer + d_pos);
	}
	else
	{
		assert(d_prevPos < d_pos);
		//get prevEntry
		DirEntry prevEntry;
		prevEntry.read(d_buffer+d_prevPos);
		prevEntry.d_recordLen += d_entry.d_recordLen;
		//write
		prevEntry.write(d_buffer + d_prevPos);
	}

	uint32_t new_block = d_dirReader->d_inodeMgr.blkIndex2BlkNum(d_inode, d_block);
	d_dirReader->d_blockMgr.setBlock(new_block, d_buffer);
	return true;
}

/*****************************************************************************/
/*****************************************************************************/

bool DirectoryIterator::end()
{
	uint32_t block_size = d_dirReader->d_sb.d_blockSize;
	uint32_t used_blocks = d_inode->d_size/block_size;
	bool b1= (d_block == used_blocks);
	return b1;
}
